package com.ego.ware.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ego.common.constant.OrderConstant;
import com.ego.common.constant.SystemConstant;
import com.ego.common.constant.WareConstant;
import com.ego.common.exception.GlobalException;
import com.ego.common.to.order.OrderItemTo;
import com.ego.common.to.order.OrderTo;
import com.ego.common.to.ware.WareLockTaskTo;
import com.ego.common.util.PageUtil;
import com.ego.common.util.Result;
import com.ego.common.vo.PageVo;
import com.ego.ware.feign.OrderFeign;
import com.ego.ware.feign.ProductFeign;
import com.ego.ware.po.PurchaseDetail;
import com.ego.common.vo.product.SkuInStockVo;
import com.ego.ware.po.WareOrderTaskDetail;
import com.ego.ware.po.WareOrderTask;
import com.ego.ware.service.WareOrderTaskDetailService;
import com.ego.ware.service.WareOrderTaskService;
import com.ego.ware.to.WareLockTo;
import com.ego.ware.vo.WareSkuSearchVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import com.ego.ware.dao.WareSkuDao;
import com.ego.ware.po.WareSku;
import com.ego.ware.service.WareSkuService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * @author KamTang
 * @program: ego
 * @description 商品库存对应实现类
 * @date 2022-11-13 13:30:29
 */
@RabbitListener(queues = "stock.release.queue")
@Service("wareSkuService")
@Slf4j
public class WareSkuServiceImpl extends ServiceImpl<WareSkuDao, WareSku> implements WareSkuService {

    @Autowired
    private ProductFeign productFeign;
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private WareOrderTaskService wareOrderTaskService;
    @Autowired
    private WareOrderTaskDetailService wareOrderTaskDetailService;
    @Autowired
    private OrderFeign orderFeign;

    @Override
    public PageVo<WareSku> search(WareSkuSearchVo search) {
        LambdaQueryWrapper<WareSku> query = Wrappers.<WareSku>lambdaQuery()
                .eq(search.getWareId() != null && search.getWareId() != 0, WareSku::getWareId, search.getWareId())
                .eq(search.getSkuId() != null && search.getSkuId() != 0, WareSku::getSkuId, search.getSkuId());
        Page<WareSku> page = this.baseMapper.selectPage(Page.of(search.getPageNum(), search.getPageSize()), query);
        return PageUtil.toPage(page);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void stock(PurchaseDetail purchaseDetail) {
        List<WareSku> wareSkus = this.baseMapper.selectList(new QueryWrapper<WareSku>()
                .eq("sku_id", purchaseDetail.getSkuId())
                .eq("ware_id", purchaseDetail.getWareId()));
        if (CollectionUtils.isEmpty(wareSkus)) {
            WareSku wareSku = WareSku.build(purchaseDetail);
            try {
                Result result = productFeign.get(purchaseDetail.getSkuId());
                if (result.getCode() == SystemConstant.Status.SUCCESS.getCode()) {
                    Map<String, Object> skuInfo = JSONObject.parseObject(JSON.toJSONString(result.get("skuInfo")));
                    String skuName = (String) skuInfo.get("skuName");
                    wareSku.setSkuName(skuName);
                }
            } catch (Exception ignored) {
            }
            this.baseMapper.insert(wareSku);
        } else {
            this.baseMapper.stock(purchaseDetail.getSkuId(), purchaseDetail.getWareId(), purchaseDetail.getSkuNum());
        }
    }

    @Override
    public List<SkuInStockVo> inStock(List<Long> skuIds) {
        return skuIds.stream().map(skuId -> {
            // 查询当前sku的总库存量
            Long count = this.baseMapper.getInventory(skuId);
            return SkuInStockVo.build(skuId, count);
        }).collect(Collectors.toList());
    }

    /**
     * 锁定库存
     *
     * @param lockTo @see
     * @return 锁定成功或者失败
     * @see WareLockTo
     */
    @Override
    @Transactional(rollbackFor = GlobalException.class)
    public Boolean lock(WareLockTo lockTo) {

        // 保存库存工作单，为了方便追溯。
        WareOrderTask wareOrderTask = new WareOrderTask();
        wareOrderTask.setOrderSn(lockTo.getOrderSn());
        wareOrderTaskService.save(wareOrderTask);

        // 找到每个商品在哪个仓库都有库存
        List<OrderItemTo> orderItems = lockTo.getLocks();
        Map<OrderItemTo, List<Long>> skuHasStock = new HashMap<>(orderItems.size(), 0.75f);
        if (!CollectionUtils.isEmpty(orderItems)) {
            // 根据skuId获取有库存的仓库信息
            List<Long> skuIds = orderItems.stream().map(OrderItemTo::getSkuId).collect(Collectors.toList());
            List<WareSku> wareSkus = this.hasStockBySkuId(skuIds);
            Map<Long, List<WareSku>> wareSkuMap = wareSkus.stream().
                    collect(Collectors.groupingBy(WareSku::getSkuId, Collectors.toList()));
            // 购买的商品都没有库存
            if (ObjectUtils.isEmpty(wareSkuMap)) {
                throw new GlobalException("购买的所有商品暂时都还没有库存",
                        SystemConstant.Status.STOCK_IS_NOT_SUFFICIENT.getCode());
            }
            for (OrderItemTo orderItem : orderItems) {
                Long skuId = orderItem.getSkuId();
                List<Long> wareIds = Optional.of(wareSkuMap.get(skuId).stream().map(WareSku::getWareId)
                        .collect(Collectors.toList())).orElse(null);
                skuHasStock.put(orderItem, wareIds);
            }
        }

        for (OrderItemTo orderItem : skuHasStock.keySet()) {
            boolean skuStocked = false;
            List<Long> wareIds = skuHasStock.get(orderItem);
            log.info("查看库存 => {}", wareIds);
            if (CollectionUtils.isEmpty(wareIds)) {
                throw new GlobalException("sku=" + orderItem.getSkuId() + "的商品暂时还没有库存",
                        SystemConstant.Status.STOCK_IS_NOT_SUFFICIENT.getCode());
            }

            // 如果没一件商品锁定成功，将当前商品锁定任务清单发送给MQ。只要一件商品锁定失败，前面锁定都要回滚。
            // 发送给MQ的消息即使要解锁记录，由于数据中回滚了，查不到对应的任务ID，
            for (Long wareId : wareIds) {
                // 锁库存
                Long count = this.baseMapper.lockSkuStock(orderItem.getSkuId(), wareId, orderItem.getSkuQuantity());
                // 锁定成功
                if (count == 1L) {
                    skuStocked = true;
                    // 保存库存工作单详情 WareOrderTaskDetailPo
                    WareOrderTaskDetail wareOrderTaskDetail = buildWareOrderTaskDetail(orderItem, wareOrderTask.getId(), wareId);
                    wareOrderTaskDetailService.save(wareOrderTaskDetail);
                    // 锁定成功发送消息，让其他服务感知
                    rabbitTemplate.convertAndSend("stock-event-exchange",
                            "stock.locked", buildLockTaskTo(wareOrderTask.getId(), wareOrderTaskDetail));
                    break;
                }
            }

            // 如果所有仓库都没锁住
            if (!skuStocked) {
                throw new GlobalException("sku=" + orderItem.getSkuId() + "的商品暂时还没有库存",
                        SystemConstant.Status.STOCK_IS_NOT_SUFFICIENT.getCode());
            }
        }

        // 能走到这肯定锁定成功了
        return true;
    }

    /**
     * 库存解锁的场景
     * 1）、下订单成功，订单过期没有支付，被系统自动取消或者被用户手动取消
     * 2）、下订单成功，库存锁定成功，接下来的业务调用失败，导致订单回滚。之前锁定的库存就要自动解锁。
     * 查询数据库关于这个订单的锁定库存信息
     * 有：只能说明库存锁定成功了，到底要不要解锁，还要看订单情况。如果订单都没有就必须解锁。而订单存在，不是解锁库存，
     * 要看订单状态，如果是“已取消”就解锁，其他任何状态都不能解锁。
     * 没有：库存锁定失败了，库存服务回滚。这种情况无需解锁
     * <p>
     * <p>
     * 只要解锁库存的消息失败，一定不要删除该消息，可以重试解锁。
     */
    @Override
    public void releaseLock(WareLockTaskTo to) {
        WareOrderTaskDetail taskDetail = wareOrderTaskDetailService.getById(to.getDetailId());
        if (ObjectUtils.isNotEmpty(taskDetail)) {
            // 解锁
            WareOrderTask task = wareOrderTaskService.getById(to.getId());
            Result orderRes = orderFeign.get(task.getOrderSn());
            if (orderRes.getCode() == SystemConstant.Status.SUCCESS.getCode()) {
                OrderTo orderTo = orderRes.getData(new TypeReference<OrderTo>() {
                });
                // 状态为“已取消”的订单才能解锁或者订单不存在（订单服务回滚）
                if (orderTo.getStatus().equals(OrderConstant.StatusEnum.CLOSED.getCode()) || ObjectUtils.isEmpty(orderTo)) {
                    // 只有库存工作单为锁定状态才能执行解锁操作。
                    if (taskDetail.lockable()) {
                        unLock(taskDetail);
                    }
                }
            } else {
                throw new GlobalException(SystemConstant.Status.FEIGN_EXCEPTION.getMsg(),
                        SystemConstant.Status.FEIGN_EXCEPTION.getCode());
            }
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void releaseLock(OrderTo orderTo) {
        String orderSn = orderTo.getOrderSn();
        // 查询最新库存状态，防止重复解锁库存。
        WareOrderTask task = wareOrderTaskService.get(orderSn);
        List<WareOrderTaskDetail> taskDetails = wareOrderTaskDetailService.getLocked(task.getId());
        for (WareOrderTaskDetail taskDetail : taskDetails) {
            unLock(taskDetail);
        }
    }

    @Override
    public List<WareSku> hasStockBySkuId(List<Long> skuIds) {
        QueryWrapper<WareSku> queryWrapper = new QueryWrapper<WareSku>()
                .in("sku_id", skuIds)
                .apply("stock - stock_locked > 0");
        return this.list(queryWrapper);
    }

    private void unLock(WareOrderTaskDetail taskDetail) {
        // 库存解锁
        this.baseMapper.unlock(taskDetail.getSkuId(), taskDetail.getWareId(), taskDetail.getSkuNum());

        // 更新库存工作单的状态为“已解锁”
        taskDetail.unlock();
        wareOrderTaskDetailService.updateById(taskDetail);
    }


    /**
     * 构建库存工作单详情
     */
    private WareOrderTaskDetail buildWareOrderTaskDetail(OrderItemTo orderItem, Long taskId, Long wareId) {
        WareOrderTaskDetail wareOrderTaskDetail = new WareOrderTaskDetail();
        wareOrderTaskDetail.setSkuId(orderItem.getSkuId());
        wareOrderTaskDetail.setWareId(wareId);
        wareOrderTaskDetail.setTaskId(taskId);
        wareOrderTaskDetail.setSkuNum(orderItem.getSkuQuantity());
        wareOrderTaskDetail.setLockStatus(WareConstant.WareOrderTaskLockStatusEnum.LOCKED.getCode());
        return wareOrderTaskDetail;
    }

    /**
     * 构建库存锁定消息TO
     */
    private WareLockTaskTo buildLockTaskTo(Long taskId, WareOrderTaskDetail taskDetail) {
        WareLockTaskTo to = new WareLockTaskTo();
        to.setId(taskId);
        to.setDetailId(taskDetail.getId());
        to.setSkuId(taskDetail.getSkuId());
        to.setSkuName(taskDetail.getSkuName());
        to.setSkuNum(taskDetail.getSkuNum());
        to.setWareId(taskDetail.getWareId());
        to.setLockStatus(taskDetail.getLockStatus());
        return to;
    }
}
